package com.sw.study.springmvc;

import com.sw.study.springmvc.annotation.Security;
import com.sw.study.springmvc.annotation.SwAutowired;
import com.sw.study.springmvc.annotation.SwController;
import com.sw.study.springmvc.annotation.SwRequestMapping;
import com.sw.study.springmvc.annotation.SwService;
import com.sw.study.springmvc.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by sw on 2020/4/8.
 * 总控制
 */
public class SWDispatchServlet extends HttpServlet {
    private Properties properties=new Properties();
    /**
     * 扫描类
     */
    private List<String> classNames=new ArrayList<>();
    /**
     * ioc
     */
    private Map<String,Object> IOC=new HashMap<>();
    /**
     * 存储method和url之间的关系
     */
/*
    private Map<String,Method> handlerMapping=new HashMap<>();
*/
    private List<Handler> handlerMapping=new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /**
         * 处理请求 根据url 找到对应的method 方法 进行请求的转发
         */
        //处理乱码
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        final String requestURI = req.getRequestURI();
        Handler handler=getHandler(req);
        if(handler==null){
            resp.getWriter().write("404 not found");
            return;
        }

        //参数绑定
        //获取所有参数类型数组，这个数组的长度就是我们最后要传入的args，就是我们要传入的args的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        Object [] paraValues=new Object[parameterTypes.length];
        //放入参数，保证和方法里的参数的形参顺序一致
        Map<String, String[]> parameterMap = req.getParameterMap();
        //加入安全判断
        boolean isExistsUser=false;
        if(null!=handler.getSecurityUsers() && handler.getSecurityUsers().length>0) {
            //不包含username参数 那么返回无权限
           if(!parameterMap.containsKey("username") ||parameterMap.get("username").length<1){
               resp.getWriter().write("未知用户，不能访问该功能");
               return;
           }
           //判断是否可以进行安全访问
           String [] params= parameterMap.get("username");
           //因为模拟 所以认定params只有一个值
            String[] securityUsers = handler.getSecurityUsers();
            for(String user:securityUsers){
               if(user.equals(params[0])){
                   isExistsUser=true;
               }
           }
            if(!isExistsUser){//无权限
                resp.getWriter().write(params[0]+"对不起，您不没有权限");
                return;
            }
        }

        // 除request和response
        for(Map.Entry<String,String[]> param:parameterMap.entrySet()){
           String value= StringUtils.join(param.getValue(),",");
           //填充数据 如果和我们的数据参数匹配上了
            if(!handler.getParamIndexMapping().containsKey(param.getKey())){
                continue;
            }
            //真实存在就找到索引位置，放入paraValues中
            Integer index = handler.getParamIndexMapping().get(param.getKey());
            paraValues[index]=value; //填充到对应到位置上
        }
        int requestIndex=handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        paraValues[requestIndex]=req;
        int responseIndex=handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        paraValues[responseIndex]=resp;
        try {
            //方法执行
            handler.getMethod().invoke(handler.getController(),paraValues);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Handler getHandler(HttpServletRequest req) {
        String requestURI = req.getRequestURI();
        for(Handler handler:handlerMapping){
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if(!matcher.matches()){
                continue;
            }
            return handler;
        }
        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1。加载配置文件
        String contextConfiguration = config.getInitParameter("contextConfiguration");
        doLoadConfiguration(contextConfiguration);

        // 2。扫描注解类
        doScan(properties.getProperty("scanPackage"));
        // 3。初始化bean信息
        try {
            doInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        // 4。建立依赖关系
        try {
            doAutowired();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        // 5。构造handlerMapping 处理器映射器将url和method建立映射关系
        initHandlerMapping();
        // 6。初始化权限信息
        initSecuritr();
        System.out.println("customer mvc  is init success");

    }

    /**
     * 初始化权限
     */
    private void initSecuritr() {
            /*
             * 1。 只有类有这个注解 那么下面的所有方法 都可以被访问
             * 2。当类无该注解，方法有的话，以方法配置为准
             * 3。当类和方法都无该注解时
             * 4。当类有注解 但是方法和类不一致时 以方法为主
             * 5。当类有注解 但是类中的方法 有的有 有的没有时 方法没有注解时 跟随类 有注解时根据自己
             *
             *
             * ---》得出；总逻辑判断
             * 1。方法包含安全注解 以方法为主
             * 2。方法不包含安全注解 以类配置为主
             * 3。类和方法都没有的话，跳过安全拦截
             */
        boolean classhave=false;
        String [] users=null;
        for (Handler handler:handlerMapping) {
            Class aclass=handler.getController().getClass();
            //判断类中安全注解
            if(aclass.isAnnotationPresent(Security.class)){
                classhave=true;
                Security security= (Security) aclass.getAnnotation(Security.class);
                users=security.value();
            }
            //判断方法
            Method method=handler.getMethod();
            if(method.isAnnotationPresent(Security.class)){//包含安全注解 以方法为主
                Security security=method.getDeclaredAnnotation(Security.class);
                handler.setSecurityUsers(security.value());
            }else if (classhave){//不包含安全注解 以类配置为主
                handler.setSecurityUsers(users);
            }
            //其它情况不考虑
        }


    }

    /**
     * 读取配置文件信息
     * @param contextConfiguration
     */
    private void doLoadConfiguration(String contextConfiguration) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfiguration);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 建立url映射 和
     */
    private void initHandlerMapping() {
        for (Map.Entry<String,Object> entry:IOC.entrySet()){
            //获取IOC当前编列的
            Class<?> aClass=entry.getValue().getClass();
            if(!aClass.isAnnotationPresent(SwController.class)){
                continue;
            }
            String baseUrl="";
            if(aClass.isAnnotationPresent(SwRequestMapping.class)){
                SwRequestMapping swRequestMapping=aClass.getAnnotation(SwRequestMapping.class);
                baseUrl=baseUrl+swRequestMapping.value();
            }
            Method [] methods=aClass.getMethods();
            for(Method m:methods){
                //没有注解
                if(!m.isAnnotationPresent(SwRequestMapping.class)){
                    continue;
                }
                SwRequestMapping meRe=m.getAnnotation(SwRequestMapping.class);
                baseUrl=baseUrl+meRe.value();

                //将method和url信息封装为一个handler
                Handler handler=new Handler(entry.getValue(),m, Pattern.compile(baseUrl));
                //处理参数
                Parameter[] parameters = m.getParameters();
                for(int i=0;i<parameters.length;i++){
                    Parameter parameter=parameters[i];
                    if(parameter.getType()==HttpServletRequest.class || parameter.getType()==HttpServletResponse.class){
                        // 特殊处理
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),i);
                    }else{
                        handler.getParamIndexMapping().put(parameter.getName(),i);
                    }
                }
                //建立url 和method的关系 类实例和参数
                handlerMapping.add(handler);
            }
        }
    }

    /**
     * 建立ioc容器和依赖关系
     */
    private void doAutowired() throws IllegalAccessException {
        for(Map.Entry<String,Object> entry:IOC.entrySet()){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for(Field field:fields){
                //是否存在属性注解
                if(!field.isAnnotationPresent(SwAutowired.class)){
                    continue;
                }
                //有注解
                SwAutowired annotation = field.getAnnotation(SwAutowired.class);
                String beanName=annotation.value();
                if("".equals(beanName)){//为空
                    beanName=field.getType().getName();//拿到类名
                }
                //强制访问
                field.setAccessible(true);
                field.set(entry.getValue(),IOC.get(beanName));
            }
        }
    }

    /**
     * 初始化所有的类
     * IOC
     */
    private void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //基于classNames缓存的类的权限定类名 以及反射技术，完成类的创建和管理
        if(classNames.size()==0){
            return;
        }
        for(String className:classNames){
            Class<?> object=Class.forName(className);
            //区分controller 和service 注解
            if(object.isAnnotationPresent(SwController.class)){
                //controller 的id此处不做过多的处理，不取value 就拿类的首字母小写作为ID，保存到Ioc中
                String simpleName = object.getSimpleName();
                String classId = lowerFirst(simpleName);
                IOC.put(classId,object.newInstance());
            }else if(object.isAnnotationPresent(SwService.class)){
                SwService swService=object.getAnnotation(SwService.class);
                //获取注解vaue
                String beanName=swService.value();
                if ("".equals(beanName.trim())){//value为空，首字母小写
                    beanName= lowerFirst(object.getSimpleName());
                }
                IOC.put(beanName,object.newInstance());
                //service 层往往有接口，此时创建一个接口名权限定类名为id 的类实例放入IOC 便于接口类进行注入
                Class<?> [] interfaces=object.getInterfaces();
                for (Class c:interfaces){
                    IOC.put(c.getName(),object.newInstance());
                }
            }
        }
    }

    /**
     * 首字母小写
     * @param str
     * @return
     */
    public String lowerFirst(String str){
        char [] chars=str.toCharArray();
        if('A'<=chars[0] && chars[0]<='Z'){
            chars[0]+=32;
        }
        return  String.valueOf(chars);
    }

    /**
     * ioc 容器
     * @param scanPackage  扫描的
     */
    private void doScan(String scanPackage){
        String packagePath=scanPackage.replaceAll("\\.","/");
        String classPath=Thread.currentThread().getContextClassLoader().getResource("").getPath()
                +packagePath;
        File pack=new File(classPath);
        File [] files=pack.listFiles();
        for (File file:files){
            if(file.isDirectory()){
                doScan(scanPackage+"."+file.getName());
            }else if(file.getName().endsWith(".class")){
                String className=scanPackage+"."+file.getName().replaceAll(".class","");
                classNames.add(className);
            }
        }
    }

}